{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# csv股票和指标数据文件\n",
    "指标数据文件生成交易信号列，策略中调用"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [],
   "source": [
    "import backtrader as bt  \n",
    "import pandas as pd\n",
    "from datetime import datetime"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 导入股票和指标数据文件\n",
    "data_stock和data_signals"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 读取csv\n",
    "data_stock = pd.read_csv('data/sh.csv') # 股票日数据\n",
    "data_signals = pd.read_csv('data/bwwmacd.csv') # 股票指标数据(大智慧导出)\n",
    "\n",
    "# 提取需要列\n",
    "selected_columns = ['日期', '开盘', '最高', '最低', '收盘', '成交量', 'MA5', 'MA10', 'MA30', 'MA60']\n",
    "data_stock = data_stock[selected_columns]\n",
    "\n",
    "# 改变列名称\n",
    "data_stock.columns = ['date', 'open', 'high', 'low', 'close', 'volume','MA5','MA10','MA30',\t'MA60']\n",
    "data_signals.columns = ['date', 'Ind1', 'Ind2', 'Ind3']\n",
    "\n",
    "# 日期列转换时间类型\n",
    "data_stock['date'] = pd.to_datetime(data_stock['date'])\n",
    "data_signals['date'] = pd.to_datetime(data_signals['date'])\n",
    "\n",
    "# 均线数据不要\n",
    "data_stock = data_stock.iloc[:, :6]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 合并"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Index(['date', 'open', 'high', 'low', 'close', 'volume'], dtype='object')\n",
      "Index(['date', 'Ind1', 'Ind2', 'Ind3'], dtype='object')\n",
      "Index(['date', 'open', 'high', 'low', 'close', 'volume', 'Ind1', 'Ind2',\n",
      "       'Ind3'],\n",
      "      dtype='object')\n"
     ]
    }
   ],
   "source": [
    "print(data_stock.columns)\n",
    "print(data_signals.columns)\n",
    "df = pd.merge(data_stock, data_signals, on='date', how='inner')\n",
    "print(df.columns)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 加入信号列\n",
    "data_signals加入signal"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Index(['date', 'open', 'high', 'low', 'close', 'volume', 'Ind1', 'Ind2',\n",
      "       'Ind3', 'signal'],\n",
      "      dtype='object')\n"
     ]
    }
   ],
   "source": [
    "# 初始化一个新的列来存储金叉死叉信号  \n",
    "df['signal'] = 0\n",
    "  \n",
    "# 使用shift函数来访问前一行的值，然后设置新的信号列  \n",
    "# 注意这里我们使用了向量化操作，而不是循环 \n",
    "crossbuy = (df['Ind2'] > df['Ind3'].shift(1)) & (df['Ind2'].shift(1) < df['Ind3'].shift(2))\n",
    "crosssell = (df['Ind2'] < df['Ind3'].shift(1)) & (df['Ind2'].shift(1) > df['Ind3'].shift(2))\n",
    "df.loc[crossbuy,'signal'] = 1 \n",
    "df.loc[crosssell,'signal'] =-1 \n",
    "  \n",
    "# 注意：这里我们使用了.shift(1)来访问前一行（即上一个交易日）的'Ind2'值  \n",
    "# 因为我们想要比较当前行的'M5'与前一行的'Ind2'  \n",
    "\n",
    "print(df.columns)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 提取需要列"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [],
   "source": [
    "selected_columns = ['date', 'open', 'high', 'low', 'close', 'volume', 'Ind1', 'signal']\n",
    "df = df[selected_columns]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "           date     open     high      low    close     volume    Ind1  signal\n",
      "0    1991-02-01   129.50   129.97   129.45   129.51        345    -2.2       0\n",
      "1    1991-02-04   129.05   129.58   129.05   129.05        553    -2.2       0\n",
      "2    1991-02-05   128.56   128.58   128.53   128.58       8562    -2.2       0\n",
      "3    1991-02-06   129.13   129.15   128.06   129.14       6641    -2.4       0\n",
      "4    1991-02-07   129.74   129.79   129.14   129.79       2133    -2.2       0\n",
      "...         ...      ...      ...      ...      ...        ...     ...     ...\n",
      "8137 2024-06-03  3085.98  3097.20  3061.28  3078.49  357563360 -1106.6       0\n",
      "8138 2024-06-04  3071.32  3095.32  3063.59  3091.20  309287584 -1146.8       0\n",
      "8139 2024-06-05  3086.05  3092.35  3064.74  3065.40  290542336 -1186.8       0\n",
      "8140 2024-06-06  3069.44  3077.67  3040.83  3048.79  369867104 -1224.4       0\n",
      "8141 2024-06-07  3053.92  3065.02  3031.04  3051.28  316342496 -1258.2       0\n",
      "\n",
      "[8142 rows x 8 columns]\n"
     ]
    }
   ],
   "source": [
    "print(df)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import quantstats as qs\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 自定义数据源类\n",
    "这两个类都继承自 bt.feeds.PandasData，意味着它们能够处理Pandas DataFrame格式的数据。\n",
    "\n",
    "在策略中，您可以通过 self.datas[0] 访问股票数据，通过 self.datas[1] 访问信号数据。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 自定义数据源，这里仅展示框架，具体实现需要根据你的CSV文件格式进行调整  \n",
    "class MyStockData(bt.feeds.PandasData):  # MyStockData 类定义了如何从Pandas DataFrame中提取股票数据。\n",
    "    # params 元组指定了日期、开盘价、最高价、最低价、收盘价和交易量（如果有的话）在DataFrame中的列索引。\n",
    "    params = (  \n",
    "        ('datetime', 0),  # 日期在DataFrame中的列索引  \n",
    "        ('open', 1),      # 开盘价在DataFrame中的列索引  \n",
    "        ('high', 2),      # 最高价在DataFrame中的列索引  \n",
    "        ('low', 3),       # 最低价在DataFrame中的列索引  \n",
    "        ('close', 4),     # 收盘价在DataFrame中的列索引  \n",
    "        ('volume', 5),    # 交易量在DataFrame中的列索引（如果有的话）  \n",
    "    )  \n",
    "  \n",
    "class MySignalData(bt.feeds.PandasData):  # MySignalData 类用于处理信号数据。\n",
    "    # 指定了日期和信号在DataFrame中的列索引。\n",
    "    params = (  \n",
    "        ('datetime', 0),  # 日期在DataFrame中的列索引  \n",
    "        ('signal', 1),    # 信号在DataFrame中的列索引，假设1为买入，-1为卖出，0为不操作  \n",
    "    )  \n",
    "  \n",
    "    # 可能需要重写lines属性，以便在策略中直接访问信号  \n",
    "    # Backtrader中，lines 是一个属性，它允许您定义可以从数据源中直接访问的“线”（即时间序列数据）。\n",
    "    lines = ('signal',)  # lines = ('signal',) 是一个元组的示例，\n",
    "    '''\n",
    "                    lines = ('signal',)  \n",
    "                    print(lines)  # 输出: ('signal',)  \n",
    "                    print(lines[0])  # 输出: signal\n",
    "    '''\n",
    "    \n",
    "  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 买卖策略"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 在策略中， self.datas[0] 访问股票数据，通过 self.datas[1] 访问信号数据。\n",
    "class MyStrategy(bt.Strategy):  \n",
    "    # params这个参数用于控制是否打印日志到控制台。\n",
    "    params = (  \n",
    "        ('printlog', True),  \n",
    "    )  # 只包含一个 两元素的子元组('printlog', True) 的元组。可以通过索引来访问这个子元组及其内部的元素。\n",
    "    '''\n",
    "                    params = (('printlog', True),)  \n",
    "                \n",
    "                    # 访问整个子元组  \n",
    "                    sub_tuple = params[0]  \n",
    "                    print(sub_tuple)  # 输出: ('printlog', True)  \n",
    "                    \n",
    "                    # 访问子元组内的第一个元素  \n",
    "                    print(sub_tuple[0])  # 输出: printlog  \n",
    "                    \n",
    "                    # 访问子元组内的第二个元素  \n",
    "                    print(sub_tuple[1])  # 输出: True  \n",
    "                    \n",
    "                    # 或者直接通过params访问  \n",
    "                    print(params[0][0])  # 输出: printlog  \n",
    "                    print(params[0][1])  # 输出: True\n",
    "    '''\n",
    "    # 用于记录日志的函数\n",
    "    def log(self, txt, dt=None):  \n",
    "        ''' Logging function fot this strategy\n",
    "            它接受两个参数：\n",
    "                要打印的文本txt和一个可选的日期时间dt。\n",
    "                如果dt未提供（即None），则使用第一个数据源（self.datas[0]）的起始日期时间。\n",
    "                然后，如果printlog参数为True，它将打印带有日期时间的文本到控制台。\n",
    "        '''  \n",
    "        if self.params.printlog:  \n",
    "            dt = dt or self.datas[0].datetime.datetime(0)  \n",
    "            print(f'{dt}, {txt}')  \n",
    "  \n",
    "    def __init__(self):  \n",
    "        # 初始化策略  \n",
    "        self.dataclose = self.datas[0].close  \n",
    "        self.datasignal = self.datas[1].lines.signal  \n",
    "  \n",
    "        # 可以在这里添加任何初始化代码  \n",
    "  \n",
    "    def next(self):  \n",
    "        # 简单示例：根据信号进行买入或卖出操作, 如果datasignal的当前值（self.datasignal[0]）...\n",
    "        if self.datasignal[0] == 2:  # 如果信号为1，买入  \n",
    "            if not self.position:  # 如果没有持仓  \n",
    "                self.log('BUY CREATE, %.2f' % self.dataclose[0])  \n",
    "                self.buy()  \n",
    "        elif self.datasignal[0] == 0:  # 如果信号为-1，卖出  \n",
    "            if self.position:  # 如果有持仓  \n",
    "                self.log('SELL CREATE, %.2f' % self.dataclose[0])  \n",
    "                self.sell() "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1991-02-21 00:00:00, BUY CREATE, 134.28\n",
      "1991-03-12 00:00:00, SELL CREATE, 128.77\n",
      "1991-04-04 00:00:00, BUY CREATE, 121.72\n",
      "1991-07-05 00:00:00, SELL CREATE, 135.96\n",
      "1991-07-30 00:00:00, BUY CREATE, 142.39\n",
      "1991-08-26 00:00:00, SELL CREATE, 171.51\n",
      "1991-10-04 00:00:00, BUY CREATE, 184.29\n",
      "1991-11-26 00:00:00, SELL CREATE, 252.97\n",
      "1991-12-11 00:00:00, BUY CREATE, 273.71\n",
      "1991-12-17 00:00:00, SELL CREATE, 278.68\n",
      "1992-01-14 00:00:00, BUY CREATE, 305.74\n",
      "1992-03-16 00:00:00, SELL CREATE, 377.05\n",
      "1992-04-13 00:00:00, BUY CREATE, 391.11\n",
      "1992-06-04 00:00:00, SELL CREATE, 1104.46\n",
      "1992-06-30 00:00:00, BUY CREATE, 1191.18\n",
      "1992-07-22 00:00:00, SELL CREATE, 1035.66\n",
      "1992-08-21 00:00:00, BUY CREATE, 916.25\n",
      "1992-09-07 00:00:00, SELL CREATE, 702.83\n",
      "1992-09-17 00:00:00, BUY CREATE, 761.72\n",
      "1992-10-13 00:00:00, SELL CREATE, 673.99\n",
      "1992-11-16 00:00:00, BUY CREATE, 399.32\n",
      "1992-12-16 00:00:00, SELL CREATE, 685.82\n",
      "1993-01-08 00:00:00, BUY CREATE, 889.47\n",
      "1993-02-12 00:00:00, SELL CREATE, 1458.76\n",
      "1993-03-19 00:00:00, BUY CREATE, 1174.99\n",
      "1993-03-26 00:00:00, SELL CREATE, 1039.76\n",
      "1993-04-06 00:00:00, BUY CREATE, 1059.62\n",
      "1993-05-04 00:00:00, SELL CREATE, 1241.16\n",
      "1993-06-07 00:00:00, BUY CREATE, 1064.98\n",
      "1993-07-01 00:00:00, SELL CREATE, 997.53\n",
      "1993-07-30 00:00:00, BUY CREATE, 881.07\n",
      "1993-08-26 00:00:00, SELL CREATE, 903.91\n",
      "1993-09-30 00:00:00, BUY CREATE, 890.27\n",
      "1993-10-19 00:00:00, SELL CREATE, 839.60\n",
      "1993-11-05 00:00:00, BUY CREATE, 814.39\n",
      "1993-12-07 00:00:00, SELL CREATE, 1022.34\n",
      "1994-01-07 00:00:00, BUY CREATE, 879.64\n",
      "1994-01-27 00:00:00, SELL CREATE, 818.60\n",
      "1994-02-21 00:00:00, BUY CREATE, 754.41\n",
      "1994-03-09 00:00:00, SELL CREATE, 716.11\n",
      "1994-03-17 00:00:00, BUY CREATE, 776.04\n",
      "1994-04-04 00:00:00, SELL CREATE, 694.38\n",
      "1994-05-04 00:00:00, BUY CREATE, 619.58\n",
      "1994-05-30 00:00:00, SELL CREATE, 544.26\n",
      "1994-06-22 00:00:00, BUY CREATE, 505.18\n",
      "1994-07-12 00:00:00, SELL CREATE, 429.21\n",
      "1994-08-04 00:00:00, BUY CREATE, 562.77\n",
      "1994-08-26 00:00:00, SELL CREATE, 707.41\n",
      "1994-09-13 00:00:00, BUY CREATE, 1033.47\n",
      "1994-09-22 00:00:00, SELL CREATE, 918.44\n",
      "1994-10-21 00:00:00, BUY CREATE, 712.71\n",
      "1994-11-25 00:00:00, SELL CREATE, 685.33\n",
      "1994-12-22 00:00:00, BUY CREATE, 673.48\n",
      "1995-01-09 00:00:00, SELL CREATE, 625.97\n",
      "1995-02-10 00:00:00, BUY CREATE, 541.38\n",
      "1995-03-16 00:00:00, SELL CREATE, 579.88\n",
      "1995-04-03 00:00:00, BUY CREATE, 656.88\n",
      "1995-04-11 00:00:00, SELL CREATE, 641.08\n",
      "1995-05-15 00:00:00, BUY CREATE, 582.04\n",
      "1995-06-07 00:00:00, SELL CREATE, 716.45\n",
      "1995-07-06 00:00:00, BUY CREATE, 637.77\n",
      "1995-08-04 00:00:00, SELL CREATE, 709.88\n",
      "1995-08-16 00:00:00, BUY CREATE, 767.66\n",
      "1995-08-21 00:00:00, SELL CREATE, 739.06\n",
      "1995-09-14 00:00:00, BUY CREATE, 758.13\n",
      "1995-09-26 00:00:00, SELL CREATE, 728.86\n",
      "1995-10-16 00:00:00, BUY CREATE, 701.34\n",
      "1995-11-13 00:00:00, SELL CREATE, 688.70\n",
      "1995-12-08 00:00:00, BUY CREATE, 636.78\n",
      "1995-12-27 00:00:00, SELL CREATE, 570.40\n",
      "1996-01-12 00:00:00, BUY CREATE, 547.11\n",
      "1996-03-19 00:00:00, SELL CREATE, 566.55\n",
      "1996-04-15 00:00:00, BUY CREATE, 584.67\n",
      "1996-05-08 00:00:00, SELL CREATE, 661.27\n",
      "1996-06-07 00:00:00, BUY CREATE, 727.34\n",
      "1996-07-02 00:00:00, SELL CREATE, 759.71\n",
      "1996-07-22 00:00:00, BUY CREATE, 884.93\n",
      "1996-08-09 00:00:00, SELL CREATE, 871.60\n",
      "1996-09-10 00:00:00, BUY CREATE, 764.44\n",
      "1996-10-22 00:00:00, SELL CREATE, 976.47\n",
      "1996-11-25 00:00:00, BUY CREATE, 976.55\n",
      "1996-12-18 00:00:00, SELL CREATE, 972.73\n",
      "1997-01-10 00:00:00, BUY CREATE, 918.40\n",
      "1997-02-21 00:00:00, SELL CREATE, 1017.13\n",
      "1997-03-06 00:00:00, BUY CREATE, 1079.13\n",
      "1997-03-20 00:00:00, SELL CREATE, 1120.71\n",
      "1997-04-23 00:00:00, BUY CREATE, 1378.84\n",
      "1997-04-28 00:00:00, SELL CREATE, 1351.99\n",
      "1997-06-06 00:00:00, BUY CREATE, 1275.30\n",
      "1997-07-10 00:00:00, SELL CREATE, 1120.15\n",
      "1997-07-22 00:00:00, BUY CREATE, 1208.24\n",
      "1997-08-11 00:00:00, SELL CREATE, 1184.08\n",
      "1997-09-02 00:00:00, BUY CREATE, 1226.30\n",
      "1997-09-17 00:00:00, SELL CREATE, 1167.55\n",
      "1997-10-14 00:00:00, BUY CREATE, 1117.92\n",
      "1997-11-06 00:00:00, SELL CREATE, 1181.12\n",
      "1997-12-11 00:00:00, BUY CREATE, 1159.16\n",
      "1998-01-14 00:00:00, SELL CREATE, 1202.21\n",
      "1998-02-19 00:00:00, BUY CREATE, 1232.58\n",
      "1998-02-25 00:00:00, SELL CREATE, 1194.67\n",
      "1998-03-20 00:00:00, BUY CREATE, 1187.50\n",
      "1998-04-22 00:00:00, SELL CREATE, 1332.18\n",
      "1998-05-26 00:00:00, BUY CREATE, 1390.41\n",
      "1998-06-11 00:00:00, SELL CREATE, 1347.67\n",
      "1998-07-02 00:00:00, BUY CREATE, 1331.65\n",
      "1998-07-08 00:00:00, SELL CREATE, 1338.98\n",
      "1998-07-17 00:00:00, BUY CREATE, 1314.71\n",
      "1998-08-12 00:00:00, SELL CREATE, 1190.55\n",
      "1998-08-31 00:00:00, BUY CREATE, 1150.22\n",
      "1998-09-29 00:00:00, SELL CREATE, 1231.74\n",
      "1998-11-10 00:00:00, BUY CREATE, 1286.55\n",
      "1998-11-26 00:00:00, SELL CREATE, 1252.81\n",
      "1998-12-24 00:00:00, BUY CREATE, 1168.98\n",
      "1999-02-03 00:00:00, SELL CREATE, 1116.86\n",
      "1999-03-10 00:00:00, BUY CREATE, 1132.73\n",
      "1999-03-30 00:00:00, SELL CREATE, 1157.50\n",
      "1999-05-12 00:00:00, BUY CREATE, 1099.99\n",
      "1999-06-17 00:00:00, SELL CREATE, 1501.00\n",
      "1999-07-30 00:00:00, BUY CREATE, 1601.45\n",
      "1999-08-19 00:00:00, SELL CREATE, 1637.74\n",
      "1999-08-30 00:00:00, BUY CREATE, 1619.03\n",
      "1999-09-10 00:00:00, SELL CREATE, 1659.92\n",
      "1999-09-17 00:00:00, BUY CREATE, 1648.55\n",
      "1999-09-28 00:00:00, SELL CREATE, 1591.43\n",
      "1999-10-27 00:00:00, BUY CREATE, 1536.81\n",
      "1999-12-10 00:00:00, SELL CREATE, 1428.93\n",
      "2000-01-10 00:00:00, BUY CREATE, 1545.11\n",
      "2000-01-28 00:00:00, SELL CREATE, 1534.99\n",
      "2000-02-16 00:00:00, BUY CREATE, 1693.10\n",
      "2000-03-02 00:00:00, SELL CREATE, 1713.01\n",
      "2000-03-30 00:00:00, BUY CREATE, 1810.98\n",
      "2000-04-17 00:00:00, SELL CREATE, 1795.05\n",
      "2000-05-24 00:00:00, BUY CREATE, 1855.49\n",
      "2000-06-15 00:00:00, SELL CREATE, 1942.28\n",
      "2000-07-17 00:00:00, BUY CREATE, 1982.47\n",
      "2000-08-10 00:00:00, SELL CREATE, 2065.93\n",
      "2000-08-14 00:00:00, BUY CREATE, 2076.30\n",
      "2000-08-24 00:00:00, SELL CREATE, 2071.15\n",
      "2000-09-26 00:00:00, BUY CREATE, 1887.81\n",
      "2000-11-09 00:00:00, SELL CREATE, 2032.48\n",
      "2000-11-20 00:00:00, BUY CREATE, 2101.37\n",
      "2000-11-24 00:00:00, SELL CREATE, 2053.37\n",
      "2000-12-26 00:00:00, BUY CREATE, 2076.26\n",
      "2001-01-18 00:00:00, SELL CREATE, 2043.11\n",
      "2001-02-26 00:00:00, BUY CREATE, 1954.68\n",
      "2001-04-03 00:00:00, SELL CREATE, 2123.05\n",
      "2001-05-21 00:00:00, BUY CREATE, 2213.59\n",
      "2001-06-06 00:00:00, SELL CREATE, 2238.50\n",
      "2001-08-15 00:00:00, BUY CREATE, 1947.35\n",
      "2001-09-05 00:00:00, SELL CREATE, 1868.27\n",
      "2001-09-13 00:00:00, BUY CREATE, 1839.90\n",
      "2001-09-26 00:00:00, SELL CREATE, 1767.81\n",
      "2001-10-29 00:00:00, BUY CREATE, 1700.47\n",
      "2001-11-19 00:00:00, SELL CREATE, 1669.89\n",
      "2001-11-27 00:00:00, BUY CREATE, 1722.58\n",
      "2001-12-12 00:00:00, SELL CREATE, 1715.17\n",
      "2002-01-15 00:00:00, BUY CREATE, 1459.65\n",
      "2002-01-17 00:00:00, SELL CREATE, 1419.51\n",
      "2002-01-25 00:00:00, BUY CREATE, 1451.48\n",
      "2002-03-15 00:00:00, SELL CREATE, 1620.97\n",
      "2002-04-23 00:00:00, BUY CREATE, 1624.54\n",
      "2002-05-22 00:00:00, SELL CREATE, 1564.48\n",
      "2002-06-10 00:00:00, BUY CREATE, 1517.23\n",
      "2002-07-12 00:00:00, SELL CREATE, 1698.29\n",
      "2002-08-13 00:00:00, BUY CREATE, 1636.63\n",
      "2002-09-11 00:00:00, SELL CREATE, 1620.35\n",
      "2002-10-11 00:00:00, BUY CREATE, 1524.05\n",
      "2002-10-15 00:00:00, SELL CREATE, 1524.72\n",
      "2002-10-23 00:00:00, BUY CREATE, 1548.96\n",
      "2002-11-18 00:00:00, SELL CREATE, 1451.29\n",
      "2002-12-05 00:00:00, BUY CREATE, 1404.88\n",
      "2002-12-31 00:00:00, SELL CREATE, 1357.65\n",
      "2003-01-17 00:00:00, BUY CREATE, 1478.67\n",
      "2003-02-17 00:00:00, SELL CREATE, 1496.52\n",
      "2003-03-31 00:00:00, BUY CREATE, 1510.56\n",
      "2003-04-25 00:00:00, SELL CREATE, 1487.14\n",
      "2003-05-27 00:00:00, BUY CREATE, 1568.34\n",
      "2003-06-18 00:00:00, SELL CREATE, 1553.87\n",
      "2003-07-15 00:00:00, BUY CREATE, 1525.43\n",
      "2003-07-30 00:00:00, SELL CREATE, 1479.65\n",
      "2003-08-12 00:00:00, BUY CREATE, 1479.28\n",
      "2003-08-27 00:00:00, SELL CREATE, 1425.36\n",
      "2003-09-08 00:00:00, BUY CREATE, 1425.72\n",
      "2003-09-18 00:00:00, SELL CREATE, 1390.48\n",
      "2003-10-13 00:00:00, BUY CREATE, 1399.65\n",
      "2003-10-30 00:00:00, SELL CREATE, 1344.63\n",
      "2003-11-20 00:00:00, BUY CREATE, 1376.50\n",
      "2003-12-17 00:00:00, SELL CREATE, 1453.99\n",
      "2004-01-12 00:00:00, BUY CREATE, 1618.51\n",
      "2004-02-19 00:00:00, SELL CREATE, 1717.01\n",
      "2004-03-22 00:00:00, BUY CREATE, 1755.68\n",
      "2004-04-16 00:00:00, SELL CREATE, 1693.86\n",
      "2004-05-18 00:00:00, BUY CREATE, 1555.43\n",
      "2004-06-15 00:00:00, SELL CREATE, 1463.77\n",
      "2004-07-02 00:00:00, BUY CREATE, 1441.18\n",
      "2004-07-28 00:00:00, SELL CREATE, 1388.81\n",
      "2004-07-29 00:00:00, BUY CREATE, 1407.33\n",
      "2004-07-30 00:00:00, SELL CREATE, 1386.20\n",
      "2004-08-09 00:00:00, BUY CREATE, 1395.84\n",
      "2004-08-13 00:00:00, SELL CREATE, 1368.45\n",
      "2004-09-03 00:00:00, BUY CREATE, 1327.11\n",
      "2004-10-12 00:00:00, SELL CREATE, 1384.43\n",
      "2004-11-08 00:00:00, BUY CREATE, 1304.22\n",
      "2004-12-03 00:00:00, SELL CREATE, 1337.18\n",
      "2005-01-06 00:00:00, BUY CREATE, 1239.43\n",
      "2005-02-02 00:00:00, SELL CREATE, 1252.50\n",
      "2005-02-23 00:00:00, BUY CREATE, 1309.47\n",
      "2005-03-14 00:00:00, SELL CREATE, 1293.50\n",
      "2005-04-08 00:00:00, BUY CREATE, 1248.52\n",
      "2005-04-27 00:00:00, SELL CREATE, 1148.75\n",
      "2005-05-20 00:00:00, BUY CREATE, 1099.26\n",
      "2005-06-29 00:00:00, SELL CREATE, 1104.99\n",
      "2005-07-26 00:00:00, BUY CREATE, 1072.80\n",
      "2005-08-23 00:00:00, SELL CREATE, 1149.95\n",
      "2005-09-14 00:00:00, BUY CREATE, 1217.26\n",
      "2005-09-23 00:00:00, SELL CREATE, 1151.98\n",
      "2005-10-25 00:00:00, BUY CREATE, 1121.91\n",
      "2005-11-04 00:00:00, SELL CREATE, 1100.04\n",
      "2005-11-14 00:00:00, BUY CREATE, 1088.65\n",
      "2005-12-07 00:00:00, SELL CREATE, 1099.61\n",
      "2005-12-21 00:00:00, BUY CREATE, 1130.75\n",
      "2006-01-19 00:00:00, SELL CREATE, 1251.56\n",
      "2006-03-22 00:00:00, BUY CREATE, 1296.70\n",
      "2006-04-20 00:00:00, SELL CREATE, 1385.90\n",
      "2006-05-16 00:00:00, BUY CREATE, 1613.25\n",
      "2006-06-01 00:00:00, SELL CREATE, 1684.20\n",
      "2006-06-30 00:00:00, BUY CREATE, 1672.21\n",
      "2006-07-18 00:00:00, SELL CREATE, 1684.25\n",
      "2006-08-18 00:00:00, BUY CREATE, 1598.01\n",
      "2006-09-18 00:00:00, SELL CREATE, 1732.98\n",
      "2006-10-18 00:00:00, BUY CREATE, 1787.18\n",
      "2006-10-23 00:00:00, SELL CREATE, 1759.39\n",
      "2006-11-24 00:00:00, BUY CREATE, 2050.81\n",
      "2006-12-19 00:00:00, SELL CREATE, 2364.16\n",
      "2007-01-15 00:00:00, BUY CREATE, 2794.70\n",
      "2007-02-01 00:00:00, SELL CREATE, 2785.42\n",
      "2007-02-28 00:00:00, BUY CREATE, 2881.07\n",
      "2007-03-13 00:00:00, SELL CREATE, 2964.79\n",
      "2007-03-26 00:00:00, BUY CREATE, 3122.81\n",
      "2007-04-09 00:00:00, SELL CREATE, 3398.95\n",
      "2007-04-16 00:00:00, BUY CREATE, 3596.43\n",
      "2007-05-11 00:00:00, SELL CREATE, 4021.66\n",
      "2007-06-21 00:00:00, BUY CREATE, 4230.81\n",
      "2007-07-03 00:00:00, SELL CREATE, 3899.72\n",
      "2007-07-17 00:00:00, BUY CREATE, 3896.18\n",
      "2007-08-15 00:00:00, SELL CREATE, 4869.87\n",
      "2007-09-05 00:00:00, BUY CREATE, 5310.72\n",
      "2007-09-11 00:00:00, SELL CREATE, 5113.97\n",
      "2007-11-08 00:00:00, BUY CREATE, 5330.02\n",
      "2008-01-11 00:00:00, SELL CREATE, 5484.68\n",
      "2008-02-20 00:00:00, BUY CREATE, 4567.02\n",
      "2008-03-14 00:00:00, SELL CREATE, 3962.66\n",
      "2008-04-09 00:00:00, BUY CREATE, 3413.89\n",
      "2008-05-23 00:00:00, SELL CREATE, 3473.09\n",
      "2008-07-01 00:00:00, BUY CREATE, 2651.60\n",
      "2008-07-31 00:00:00, SELL CREATE, 2775.71\n",
      "2008-09-01 00:00:00, BUY CREATE, 2325.12\n",
      "2008-10-16 00:00:00, SELL CREATE, 1909.93\n",
      "2008-11-07 00:00:00, BUY CREATE, 1747.71\n",
      "2008-12-05 00:00:00, SELL CREATE, 2018.65\n",
      "2009-01-15 00:00:00, BUY CREATE, 1920.20\n",
      "2009-02-23 00:00:00, SELL CREATE, 2305.77\n",
      "2009-03-20 00:00:00, BUY CREATE, 2281.08\n",
      "2009-04-15 00:00:00, SELL CREATE, 2536.05\n",
      "2009-05-15 00:00:00, BUY CREATE, 2645.26\n",
      "2009-06-01 00:00:00, SELL CREATE, 2721.28\n",
      "2009-06-15 00:00:00, BUY CREATE, 2789.54\n",
      "2009-07-29 00:00:00, SELL CREATE, 3266.42\n",
      "2009-09-03 00:00:00, BUY CREATE, 2845.02\n",
      "2009-09-29 00:00:00, SELL CREATE, 2754.54\n",
      "2009-10-22 00:00:00, BUY CREATE, 3051.40\n",
      "2009-11-10 00:00:00, SELL CREATE, 3178.61\n",
      "2009-11-13 00:00:00, BUY CREATE, 3187.63\n",
      "2009-11-26 00:00:00, SELL CREATE, 3170.98\n",
      "2010-01-05 00:00:00, BUY CREATE, 3282.16\n",
      "2010-01-27 00:00:00, SELL CREATE, 2986.60\n",
      "2010-02-23 00:00:00, BUY CREATE, 2982.57\n",
      "2010-03-16 00:00:00, SELL CREATE, 2992.84\n",
      "2010-04-01 00:00:00, BUY CREATE, 3147.40\n",
      "2010-04-19 00:00:00, SELL CREATE, 2980.29\n",
      "2010-05-25 00:00:00, BUY CREATE, 2622.62\n",
      "2010-06-29 00:00:00, SELL CREATE, 2427.05\n",
      "2010-07-19 00:00:00, BUY CREATE, 2475.41\n",
      "2010-08-11 00:00:00, SELL CREATE, 2607.49\n",
      "2010-09-13 00:00:00, BUY CREATE, 2688.32\n",
      "2010-09-21 00:00:00, SELL CREATE, 2591.55\n",
      "2010-10-18 00:00:00, BUY CREATE, 2955.22\n",
      "2010-11-16 00:00:00, SELL CREATE, 2894.53\n",
      "2010-12-09 00:00:00, BUY CREATE, 2810.93\n",
      "2010-12-31 00:00:00, SELL CREATE, 2808.07\n",
      "2011-01-17 00:00:00, BUY CREATE, 2706.65\n",
      "2011-01-24 00:00:00, SELL CREATE, 2695.72\n",
      "2011-02-09 00:00:00, BUY CREATE, 2774.06\n",
      "2011-03-07 00:00:00, SELL CREATE, 2996.21\n",
      "2011-04-13 00:00:00, BUY CREATE, 3050.38\n",
      "2011-04-28 00:00:00, SELL CREATE, 2887.04\n",
      "2011-05-18 00:00:00, BUY CREATE, 2872.77\n",
      "2011-06-01 00:00:00, SELL CREATE, 2743.57\n",
      "2011-06-14 00:00:00, BUY CREATE, 2730.04\n",
      "2011-07-21 00:00:00, SELL CREATE, 2765.88\n",
      "2011-08-22 00:00:00, BUY CREATE, 2515.86\n",
      "2011-09-14 00:00:00, SELL CREATE, 2484.83\n",
      "2011-10-21 00:00:00, BUY CREATE, 2317.28\n",
      "2011-11-21 00:00:00, SELL CREATE, 2415.12\n",
      "2011-12-28 00:00:00, BUY CREATE, 2170.01\n",
      "2012-02-23 00:00:00, SELL CREATE, 2409.55\n",
      "2012-04-16 00:00:00, BUY CREATE, 2357.03\n",
      "2012-05-11 00:00:00, SELL CREATE, 2394.98\n",
      "2012-06-08 00:00:00, BUY CREATE, 2281.45\n",
      "2012-06-14 00:00:00, SELL CREATE, 2295.95\n",
      "2012-06-20 00:00:00, BUY CREATE, 2292.87\n",
      "2012-07-04 00:00:00, SELL CREATE, 2227.31\n",
      "2012-07-18 00:00:00, BUY CREATE, 2169.10\n",
      "2012-08-28 00:00:00, SELL CREATE, 2073.14\n",
      "2012-09-13 00:00:00, BUY CREATE, 2110.37\n",
      "2012-09-26 00:00:00, SELL CREATE, 2004.17\n",
      "2012-10-17 00:00:00, BUY CREATE, 2105.62\n",
      "2012-11-01 00:00:00, SELL CREATE, 2104.42\n",
      "2012-12-06 00:00:00, BUY CREATE, 2029.24\n",
      "2013-01-10 00:00:00, SELL CREATE, 2283.65\n",
      "2013-03-26 00:00:00, BUY CREATE, 2297.66\n",
      "2013-04-15 00:00:00, SELL CREATE, 2181.93\n",
      "2013-04-23 00:00:00, BUY CREATE, 2184.54\n",
      "2013-06-04 00:00:00, SELL CREATE, 2272.41\n",
      "2013-07-08 00:00:00, BUY CREATE, 1958.27\n",
      "2013-08-07 00:00:00, SELL CREATE, 2046.78\n",
      "2013-08-14 00:00:00, BUY CREATE, 2100.13\n",
      "2013-08-21 00:00:00, SELL CREATE, 2072.96\n",
      "2013-09-13 00:00:00, BUY CREATE, 2236.22\n",
      "2013-09-18 00:00:00, SELL CREATE, 2191.85\n",
      "2013-10-22 00:00:00, BUY CREATE, 2210.64\n",
      "2013-10-29 00:00:00, SELL CREATE, 2128.86\n",
      "2013-11-18 00:00:00, BUY CREATE, 2197.22\n",
      "2013-12-12 00:00:00, SELL CREATE, 2202.80\n",
      "2014-01-13 00:00:00, BUY CREATE, 2009.56\n",
      "2014-02-26 00:00:00, SELL CREATE, 2041.25\n",
      "2014-03-24 00:00:00, BUY CREATE, 2066.28\n",
      "2014-04-24 00:00:00, SELL CREATE, 2057.03\n",
      "2014-05-19 00:00:00, BUY CREATE, 2005.18\n",
      "2014-06-19 00:00:00, SELL CREATE, 2023.73\n",
      "2014-07-04 00:00:00, BUY CREATE, 2059.37\n",
      "2014-07-22 00:00:00, SELL CREATE, 2075.48\n",
      "2014-08-05 00:00:00, BUY CREATE, 2219.95\n",
      "2014-08-18 00:00:00, SELL CREATE, 2239.47\n",
      "2014-09-12 00:00:00, BUY CREATE, 2331.95\n",
      "2014-09-29 00:00:00, SELL CREATE, 2357.71\n",
      "2014-11-07 00:00:00, BUY CREATE, 2418.16\n",
      "2014-12-17 00:00:00, SELL CREATE, 3061.02\n",
      "2015-01-15 00:00:00, BUY CREATE, 3336.45\n",
      "2015-02-10 00:00:00, SELL CREATE, 3141.59\n",
      "2015-03-02 00:00:00, BUY CREATE, 3336.28\n",
      "2015-04-08 00:00:00, SELL CREATE, 3994.81\n",
      "2015-05-21 00:00:00, BUY CREATE, 4529.42\n",
      "2015-06-15 00:00:00, SELL CREATE, 5062.99\n",
      "2015-07-16 00:00:00, BUY CREATE, 3823.18\n",
      "2015-08-24 00:00:00, SELL CREATE, 3209.91\n",
      "2015-09-17 00:00:00, BUY CREATE, 3086.06\n",
      "2015-10-29 00:00:00, SELL CREATE, 3387.32\n",
      "2015-12-21 00:00:00, BUY CREATE, 3642.47\n",
      "2016-01-08 00:00:00, SELL CREATE, 3186.41\n",
      "2016-02-01 00:00:00, BUY CREATE, 2688.85\n",
      "2016-03-08 00:00:00, SELL CREATE, 2901.39\n",
      "2016-03-22 00:00:00, BUY CREATE, 2999.36\n",
      "2016-04-11 00:00:00, SELL CREATE, 3033.96\n",
      "2016-05-25 00:00:00, BUY CREATE, 2815.09\n",
      "2016-06-24 00:00:00, SELL CREATE, 2854.29\n",
      "2016-07-06 00:00:00, BUY CREATE, 3017.29\n",
      "2016-07-21 00:00:00, SELL CREATE, 3039.01\n",
      "2016-08-17 00:00:00, BUY CREATE, 3109.55\n",
      "2016-09-05 00:00:00, SELL CREATE, 3072.10\n",
      "2016-10-11 00:00:00, BUY CREATE, 3065.25\n",
      "2016-11-04 00:00:00, SELL CREATE, 3125.32\n",
      "2016-11-18 00:00:00, BUY CREATE, 3192.86\n",
      "2016-11-30 00:00:00, SELL CREATE, 3250.03\n",
      "2016-12-27 00:00:00, BUY CREATE, 3114.66\n",
      "2017-01-20 00:00:00, SELL CREATE, 3123.14\n",
      "2017-02-09 00:00:00, BUY CREATE, 3183.18\n",
      "2017-03-02 00:00:00, SELL CREATE, 3230.03\n",
      "2017-05-10 00:00:00, BUY CREATE, 3052.79\n",
      "2017-07-03 00:00:00, SELL CREATE, 3195.91\n",
      "2017-08-04 00:00:00, BUY CREATE, 3262.08\n",
      "2017-08-24 00:00:00, SELL CREATE, 3271.51\n",
      "2017-08-28 00:00:00, BUY CREATE, 3362.65\n",
      "2017-09-15 00:00:00, SELL CREATE, 3353.62\n",
      "2017-10-19 00:00:00, BUY CREATE, 3370.17\n",
      "2017-10-31 00:00:00, SELL CREATE, 3393.34\n",
      "2017-11-13 00:00:00, BUY CREATE, 3447.84\n",
      "2017-11-23 00:00:00, SELL CREATE, 3351.92\n",
      "2017-12-08 00:00:00, BUY CREATE, 3289.99\n",
      "2018-01-18 00:00:00, SELL CREATE, 3474.75\n",
      "2018-02-28 00:00:00, BUY CREATE, 3259.41\n",
      "2018-03-23 00:00:00, SELL CREATE, 3152.76\n",
      "2018-04-18 00:00:00, BUY CREATE, 3091.40\n",
      "2018-04-24 00:00:00, SELL CREATE, 3128.93\n",
      "2018-05-10 00:00:00, BUY CREATE, 3174.41\n",
      "2018-05-30 00:00:00, SELL CREATE, 3041.44\n",
      "2018-07-04 00:00:00, BUY CREATE, 2759.13\n",
      "2018-08-03 00:00:00, SELL CREATE, 2740.44\n",
      "2018-08-30 00:00:00, BUY CREATE, 2737.74\n",
      "2018-09-14 00:00:00, SELL CREATE, 2681.64\n",
      "2018-09-25 00:00:00, BUY CREATE, 2781.14\n",
      "2018-10-16 00:00:00, SELL CREATE, 2546.33\n",
      "2018-10-31 00:00:00, BUY CREATE, 2602.78\n",
      "2018-11-26 00:00:00, SELL CREATE, 2575.81\n",
      "2019-01-08 00:00:00, BUY CREATE, 2526.46\n",
      "2019-01-31 00:00:00, SELL CREATE, 2584.57\n",
      "2019-02-20 00:00:00, BUY CREATE, 2761.22\n",
      "2019-03-13 00:00:00, SELL CREATE, 3026.95\n",
      "2019-04-15 00:00:00, BUY CREATE, 3177.79\n",
      "2019-04-19 00:00:00, SELL CREATE, 3270.80\n",
      "2019-05-21 00:00:00, BUY CREATE, 2905.97\n",
      "2019-07-09 00:00:00, SELL CREATE, 2928.23\n",
      "2019-08-06 00:00:00, BUY CREATE, 2777.56\n",
      "2019-08-12 00:00:00, SELL CREATE, 2814.99\n",
      "2019-08-20 00:00:00, BUY CREATE, 2880.00\n",
      "2019-09-18 00:00:00, SELL CREATE, 2985.66\n",
      "2019-10-24 00:00:00, BUY CREATE, 2940.92\n",
      "2019-11-12 00:00:00, SELL CREATE, 2914.82\n",
      "2019-11-27 00:00:00, BUY CREATE, 2903.19\n",
      "2020-01-02 00:00:00, SELL CREATE, 3085.20\n",
      "2020-02-19 00:00:00, BUY CREATE, 2975.40\n",
      "2020-03-12 00:00:00, SELL CREATE, 2923.49\n",
      "2020-04-07 00:00:00, BUY CREATE, 2820.76\n",
      "2020-04-30 00:00:00, SELL CREATE, 2860.08\n",
      "2020-05-20 00:00:00, BUY CREATE, 2883.74\n",
      "2020-05-25 00:00:00, SELL CREATE, 2817.97\n",
      "2020-06-08 00:00:00, BUY CREATE, 2937.77\n",
      "2020-06-23 00:00:00, SELL CREATE, 2970.62\n",
      "2020-07-07 00:00:00, BUY CREATE, 3345.34\n",
      "2020-07-23 00:00:00, SELL CREATE, 3325.11\n",
      "2020-08-14 00:00:00, BUY CREATE, 3360.10\n",
      "2020-08-26 00:00:00, SELL CREATE, 3329.74\n",
      "2020-09-29 00:00:00, BUY CREATE, 3224.36\n",
      "2020-11-02 00:00:00, SELL CREATE, 3225.12\n",
      "2020-11-13 00:00:00, BUY CREATE, 3310.10\n",
      "2020-12-04 00:00:00, SELL CREATE, 3444.58\n",
      "2021-01-05 00:00:00, BUY CREATE, 3528.68\n",
      "2021-02-05 00:00:00, SELL CREATE, 3496.33\n",
      "2021-02-23 00:00:00, BUY CREATE, 3636.36\n",
      "2021-03-16 00:00:00, SELL CREATE, 3446.73\n",
      "2021-05-20 00:00:00, BUY CREATE, 3506.94\n",
      "2021-06-16 00:00:00, SELL CREATE, 3518.33\n",
      "2021-07-13 00:00:00, BUY CREATE, 3566.52\n",
      "2021-07-28 00:00:00, SELL CREATE, 3361.59\n",
      "2021-08-11 00:00:00, BUY CREATE, 3532.62\n",
      "2021-09-06 00:00:00, SELL CREATE, 3621.86\n",
      "2021-09-09 00:00:00, BUY CREATE, 3693.13\n",
      "2021-09-17 00:00:00, SELL CREATE, 3613.97\n",
      "2021-10-25 00:00:00, BUY CREATE, 3609.86\n",
      "2021-12-06 00:00:00, SELL CREATE, 3589.31\n",
      "2022-02-16 00:00:00, BUY CREATE, 3465.83\n",
      "2022-03-09 00:00:00, SELL CREATE, 3256.39\n",
      "2022-03-29 00:00:00, BUY CREATE, 3203.94\n",
      "2022-04-20 00:00:00, SELL CREATE, 3151.05\n",
      "2022-05-13 00:00:00, BUY CREATE, 3084.28\n",
      "2022-06-10 00:00:00, SELL CREATE, 3284.83\n",
      "2022-07-28 00:00:00, BUY CREATE, 3282.58\n",
      "2022-08-10 00:00:00, SELL CREATE, 3230.02\n",
      "2022-08-17 00:00:00, BUY CREATE, 3292.53\n",
      "2022-09-01 00:00:00, SELL CREATE, 3184.98\n",
      "2022-10-17 00:00:00, BUY CREATE, 3084.94\n",
      "2022-11-25 00:00:00, SELL CREATE, 3101.69\n",
      "2023-01-09 00:00:00, BUY CREATE, 3176.08\n",
      "2023-02-16 00:00:00, SELL CREATE, 3249.03\n",
      "2023-03-30 00:00:00, BUY CREATE, 3261.25\n",
      "2023-04-27 00:00:00, SELL CREATE, 3285.88\n",
      "2023-05-11 00:00:00, BUY CREATE, 3309.55\n",
      "2023-05-31 00:00:00, SELL CREATE, 3204.56\n",
      "2023-06-16 00:00:00, BUY CREATE, 3273.33\n",
      "2023-07-24 00:00:00, SELL CREATE, 3164.16\n",
      "2023-09-04 00:00:00, BUY CREATE, 3177.06\n",
      "2023-09-26 00:00:00, SELL CREATE, 3102.27\n",
      "2023-11-01 00:00:00, BUY CREATE, 3023.08\n",
      "2023-11-27 00:00:00, SELL CREATE, 3031.70\n",
      "2024-01-03 00:00:00, BUY CREATE, 2967.25\n",
      "2024-01-22 00:00:00, SELL CREATE, 2756.34\n",
      "2024-02-20 00:00:00, BUY CREATE, 2922.73\n",
      "2024-03-19 00:00:00, SELL CREATE, 3062.76\n",
      "2024-05-06 00:00:00, BUY CREATE, 3140.72\n",
      "2024-05-27 00:00:00, SELL CREATE, 3124.04\n"
     ]
    }
   ],
   "source": [
    "\n",
    "  \n",
    "# 创建Cerebro引擎  \n",
    "cerebro = bt.Cerebro()  \n",
    "\n",
    "# # 设置回测时间范围\n",
    "# start_date = datetime(1995, 10, 5)\n",
    "# end_date = datetime(2024, 5, 22)\n",
    "  \n",
    "# 添加数据源  \n",
    "datafeed_stock = MyStockData(dataname=data_stock)  \n",
    "datafeed_signal = MySignalData(dataname=data_signals)  \n",
    "\n",
    "# 将数据源添加到cerebro  \n",
    "cerebro.adddata(datafeed_stock)  \n",
    "cerebro.adddata(datafeed_signal)  \n",
    "  \n",
    "# 添加策略  \n",
    "cerebro.addstrategy(MyStrategy)  \n",
    "  \n",
    "# 设置初始资金和手续费\n",
    "start_cash = 1000000\n",
    "cerebro.broker.setcash(start_cash)\n",
    "#cerebro.broker.setcommission(commission=0.002)\n",
    "\n",
    "# 添加策略分析指标\n",
    "cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name='tradeanalyzer')  \n",
    "cerebro.addanalyzer(bt.analyzers.AnnualReturn, _name='annualReturn')  \n",
    "cerebro.addanalyzer(bt.analyzers.Returns, _name='annualizedReturns', tann=252)  # 使用 'annualizedReturns' 代替 '_Returns'  \n",
    "cerebro.addanalyzer(bt.analyzers.DrawDown, _name='drawdown')  \n",
    "cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpeRatio')  # 使用 'sharpeRatio' 代替 'sharpe'  \n",
    "cerebro.addanalyzer(bt.analyzers.Returns, _name='totalReturns')  # 使用 'totalReturns' 代替重复的 'returns'  \n",
    "# 假设 'TimeReturn' 是有效的分析器  \n",
    "cerebro.addanalyzer(bt.analyzers.TimeReturn, _name='timeReturn')  # 假设这是有效的\n",
    "  \n",
    "# 运行回测  \n",
    "results=cerebro.run()  \n",
    "  \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "最后投资金额：1004274.2\n"
     ]
    }
   ],
   "source": [
    "print(f'最后投资金额：{round(cerebro.broker.getvalue(), 2)}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n    if (typeof WebSocket !== 'undefined') {\n        return WebSocket;\n    } else if (typeof MozWebSocket !== 'undefined') {\n        return MozWebSocket;\n    } else {\n        alert(\n            'Your browser does not have WebSocket support. ' +\n                'Please try Chrome, Safari or Firefox ≥ 6. ' +\n                'Firefox 4 and 5 are also supported but you ' +\n                'have to enable WebSockets in about:config.'\n        );\n    }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n    this.id = figure_id;\n\n    this.ws = websocket;\n\n    this.supports_binary = this.ws.binaryType !== undefined;\n\n    if (!this.supports_binary) {\n        var warnings = document.getElementById('mpl-warnings');\n        if (warnings) {\n            warnings.style.display = 'block';\n            warnings.textContent =\n                'This browser does not support binary websocket messages. ' +\n                'Performance may be slow.';\n        }\n    }\n\n    this.imageObj = new Image();\n\n    this.context = undefined;\n    this.message = undefined;\n    this.canvas = undefined;\n    this.rubberband_canvas = undefined;\n    this.rubberband_context = undefined;\n    this.format_dropdown = undefined;\n\n    this.image_mode = 'full';\n\n    this.root = document.createElement('div');\n    this.root.setAttribute('style', 'display: inline-block');\n    this._root_extra_style(this.root);\n\n    parent_element.appendChild(this.root);\n\n    this._init_header(this);\n    this._init_canvas(this);\n    this._init_toolbar(this);\n\n    var fig = this;\n\n    this.waiting = false;\n\n    this.ws.onopen = function () {\n        fig.send_message('supports_binary', { value: fig.supports_binary });\n        fig.send_message('send_image_mode', {});\n        if (fig.ratio !== 1) {\n            fig.send_message('set_device_pixel_ratio', {\n                device_pixel_ratio: fig.ratio,\n            });\n        }\n        fig.send_message('refresh', {});\n    };\n\n    this.imageObj.onload = function () {\n        if (fig.image_mode === 'full') {\n            // Full images could contain transparency (where diff images\n            // almost always do), so we need to clear the canvas so that\n            // there is no ghosting.\n            fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n        }\n        fig.context.drawImage(fig.imageObj, 0, 0);\n    };\n\n    this.imageObj.onunload = function () {\n        fig.ws.close();\n    };\n\n    this.ws.onmessage = this._make_on_message_function(this);\n\n    this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n    var titlebar = document.createElement('div');\n    titlebar.classList =\n        'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n    var titletext = document.createElement('div');\n    titletext.classList = 'ui-dialog-title';\n    titletext.setAttribute(\n        'style',\n        'width: 100%; text-align: center; padding: 3px;'\n    );\n    titlebar.appendChild(titletext);\n    this.root.appendChild(titlebar);\n    this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n    var fig = this;\n\n    var canvas_div = (this.canvas_div = document.createElement('div'));\n    canvas_div.setAttribute('tabindex', '0');\n    canvas_div.setAttribute(\n        'style',\n        'border: 1px solid #ddd;' +\n            'box-sizing: content-box;' +\n            'clear: both;' +\n            'min-height: 1px;' +\n            'min-width: 1px;' +\n            'outline: 0;' +\n            'overflow: hidden;' +\n            'position: relative;' +\n            'resize: both;' +\n            'z-index: 2;'\n    );\n\n    function on_keyboard_event_closure(name) {\n        return function (event) {\n            return fig.key_event(event, name);\n        };\n    }\n\n    canvas_div.addEventListener(\n        'keydown',\n        on_keyboard_event_closure('key_press')\n    );\n    canvas_div.addEventListener(\n        'keyup',\n        on_keyboard_event_closure('key_release')\n    );\n\n    this._canvas_extra_style(canvas_div);\n    this.root.appendChild(canvas_div);\n\n    var canvas = (this.canvas = document.createElement('canvas'));\n    canvas.classList.add('mpl-canvas');\n    canvas.setAttribute(\n        'style',\n        'box-sizing: content-box;' +\n            'pointer-events: none;' +\n            'position: relative;' +\n            'z-index: 0;'\n    );\n\n    this.context = canvas.getContext('2d');\n\n    var backingStore =\n        this.context.backingStorePixelRatio ||\n        this.context.webkitBackingStorePixelRatio ||\n        this.context.mozBackingStorePixelRatio ||\n        this.context.msBackingStorePixelRatio ||\n        this.context.oBackingStorePixelRatio ||\n        this.context.backingStorePixelRatio ||\n        1;\n\n    this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n    var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n        'canvas'\n    ));\n    rubberband_canvas.setAttribute(\n        'style',\n        'box-sizing: content-box;' +\n            'left: 0;' +\n            'pointer-events: none;' +\n            'position: absolute;' +\n            'top: 0;' +\n            'z-index: 1;'\n    );\n\n    // Apply a ponyfill if ResizeObserver is not implemented by browser.\n    if (this.ResizeObserver === undefined) {\n        if (window.ResizeObserver !== undefined) {\n            this.ResizeObserver = window.ResizeObserver;\n        } else {\n            var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n            this.ResizeObserver = obs.ResizeObserver;\n        }\n    }\n\n    this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n        // There's no need to resize if the WebSocket is not connected:\n        // - If it is still connecting, then we will get an initial resize from\n        //   Python once it connects.\n        // - If it has disconnected, then resizing will clear the canvas and\n        //   never get anything back to refill it, so better to not resize and\n        //   keep something visible.\n        if (fig.ws.readyState != 1) {\n            return;\n        }\n        var nentries = entries.length;\n        for (var i = 0; i < nentries; i++) {\n            var entry = entries[i];\n            var width, height;\n            if (entry.contentBoxSize) {\n                if (entry.contentBoxSize instanceof Array) {\n                    // Chrome 84 implements new version of spec.\n                    width = entry.contentBoxSize[0].inlineSize;\n                    height = entry.contentBoxSize[0].blockSize;\n                } else {\n                    // Firefox implements old version of spec.\n                    width = entry.contentBoxSize.inlineSize;\n                    height = entry.contentBoxSize.blockSize;\n                }\n            } else {\n                // Chrome <84 implements even older version of spec.\n                width = entry.contentRect.width;\n                height = entry.contentRect.height;\n            }\n\n            // Keep the size of the canvas and rubber band canvas in sync with\n            // the canvas container.\n            if (entry.devicePixelContentBoxSize) {\n                // Chrome 84 implements new version of spec.\n                canvas.setAttribute(\n                    'width',\n                    entry.devicePixelContentBoxSize[0].inlineSize\n                );\n                canvas.setAttribute(\n                    'height',\n                    entry.devicePixelContentBoxSize[0].blockSize\n                );\n            } else {\n                canvas.setAttribute('width', width * fig.ratio);\n                canvas.setAttribute('height', height * fig.ratio);\n            }\n            /* This rescales the canvas back to display pixels, so that it\n             * appears correct on HiDPI screens. */\n            canvas.style.width = width + 'px';\n            canvas.style.height = height + 'px';\n\n            rubberband_canvas.setAttribute('width', width);\n            rubberband_canvas.setAttribute('height', height);\n\n            // And update the size in Python. We ignore the initial 0/0 size\n            // that occurs as the element is placed into the DOM, which should\n            // otherwise not happen due to the minimum size styling.\n            if (width != 0 && height != 0) {\n                fig.request_resize(width, height);\n            }\n        }\n    });\n    this.resizeObserverInstance.observe(canvas_div);\n\n    function on_mouse_event_closure(name) {\n        /* User Agent sniffing is bad, but WebKit is busted:\n         * https://bugs.webkit.org/show_bug.cgi?id=144526\n         * https://bugs.webkit.org/show_bug.cgi?id=181818\n         * The worst that happens here is that they get an extra browser\n         * selection when dragging, if this check fails to catch them.\n         */\n        var UA = navigator.userAgent;\n        var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n        if(isWebKit) {\n            return function (event) {\n                /* This prevents the web browser from automatically changing to\n                 * the text insertion cursor when the button is pressed. We\n                 * want to control all of the cursor setting manually through\n                 * the 'cursor' event from matplotlib */\n                event.preventDefault()\n                return fig.mouse_event(event, name);\n            };\n        } else {\n            return function (event) {\n                return fig.mouse_event(event, name);\n            };\n        }\n    }\n\n    canvas_div.addEventListener(\n        'mousedown',\n        on_mouse_event_closure('button_press')\n    );\n    canvas_div.addEventListener(\n        'mouseup',\n        on_mouse_event_closure('button_release')\n    );\n    canvas_div.addEventListener(\n        'dblclick',\n        on_mouse_event_closure('dblclick')\n    );\n    // Throttle sequential mouse events to 1 every 20ms.\n    canvas_div.addEventListener(\n        'mousemove',\n        on_mouse_event_closure('motion_notify')\n    );\n\n    canvas_div.addEventListener(\n        'mouseenter',\n        on_mouse_event_closure('figure_enter')\n    );\n    canvas_div.addEventListener(\n        'mouseleave',\n        on_mouse_event_closure('figure_leave')\n    );\n\n    canvas_div.addEventListener('wheel', function (event) {\n        if (event.deltaY < 0) {\n            event.step = 1;\n        } else {\n            event.step = -1;\n        }\n        on_mouse_event_closure('scroll')(event);\n    });\n\n    canvas_div.appendChild(canvas);\n    canvas_div.appendChild(rubberband_canvas);\n\n    this.rubberband_context = rubberband_canvas.getContext('2d');\n    this.rubberband_context.strokeStyle = '#000000';\n\n    this._resize_canvas = function (width, height, forward) {\n        if (forward) {\n            canvas_div.style.width = width + 'px';\n            canvas_div.style.height = height + 'px';\n        }\n    };\n\n    // Disable right mouse context menu.\n    canvas_div.addEventListener('contextmenu', function (_e) {\n        event.preventDefault();\n        return false;\n    });\n\n    function set_focus() {\n        canvas.focus();\n        canvas_div.focus();\n    }\n\n    window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n    var fig = this;\n\n    var toolbar = document.createElement('div');\n    toolbar.classList = 'mpl-toolbar';\n    this.root.appendChild(toolbar);\n\n    function on_click_closure(name) {\n        return function (_event) {\n            return fig.toolbar_button_onclick(name);\n        };\n    }\n\n    function on_mouseover_closure(tooltip) {\n        return function (event) {\n            if (!event.currentTarget.disabled) {\n                return fig.toolbar_button_onmouseover(tooltip);\n            }\n        };\n    }\n\n    fig.buttons = {};\n    var buttonGroup = document.createElement('div');\n    buttonGroup.classList = 'mpl-button-group';\n    for (var toolbar_ind in mpl.toolbar_items) {\n        var name = mpl.toolbar_items[toolbar_ind][0];\n        var tooltip = mpl.toolbar_items[toolbar_ind][1];\n        var image = mpl.toolbar_items[toolbar_ind][2];\n        var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n        if (!name) {\n            /* Instead of a spacer, we start a new button group. */\n            if (buttonGroup.hasChildNodes()) {\n                toolbar.appendChild(buttonGroup);\n            }\n            buttonGroup = document.createElement('div');\n            buttonGroup.classList = 'mpl-button-group';\n            continue;\n        }\n\n        var button = (fig.buttons[name] = document.createElement('button'));\n        button.classList = 'mpl-widget';\n        button.setAttribute('role', 'button');\n        button.setAttribute('aria-disabled', 'false');\n        button.addEventListener('click', on_click_closure(method_name));\n        button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n        var icon_img = document.createElement('img');\n        icon_img.src = '_images/' + image + '.png';\n        icon_img.srcset = '_images/' + image + '_large.png 2x';\n        icon_img.alt = tooltip;\n        button.appendChild(icon_img);\n\n        buttonGroup.appendChild(button);\n    }\n\n    if (buttonGroup.hasChildNodes()) {\n        toolbar.appendChild(buttonGroup);\n    }\n\n    var fmt_picker = document.createElement('select');\n    fmt_picker.classList = 'mpl-widget';\n    toolbar.appendChild(fmt_picker);\n    this.format_dropdown = fmt_picker;\n\n    for (var ind in mpl.extensions) {\n        var fmt = mpl.extensions[ind];\n        var option = document.createElement('option');\n        option.selected = fmt === mpl.default_extension;\n        option.innerHTML = fmt;\n        fmt_picker.appendChild(option);\n    }\n\n    var status_bar = document.createElement('span');\n    status_bar.classList = 'mpl-message';\n    toolbar.appendChild(status_bar);\n    this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n    // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n    // which will in turn request a refresh of the image.\n    this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n    properties['type'] = type;\n    properties['figure_id'] = this.id;\n    this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n    if (!this.waiting) {\n        this.waiting = true;\n        this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n    }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n    var format_dropdown = fig.format_dropdown;\n    var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n    fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n    var size = msg['size'];\n    if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n        fig._resize_canvas(size[0], size[1], msg['forward']);\n        fig.send_message('refresh', {});\n    }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n    var x0 = msg['x0'] / fig.ratio;\n    var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n    var x1 = msg['x1'] / fig.ratio;\n    var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n    x0 = Math.floor(x0) + 0.5;\n    y0 = Math.floor(y0) + 0.5;\n    x1 = Math.floor(x1) + 0.5;\n    y1 = Math.floor(y1) + 0.5;\n    var min_x = Math.min(x0, x1);\n    var min_y = Math.min(y0, y1);\n    var width = Math.abs(x1 - x0);\n    var height = Math.abs(y1 - y0);\n\n    fig.rubberband_context.clearRect(\n        0,\n        0,\n        fig.canvas.width / fig.ratio,\n        fig.canvas.height / fig.ratio\n    );\n\n    fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n    // Updates the figure title.\n    fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n    fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n    fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n    // Request the server to send over a new figure.\n    fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n    fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n    for (var key in msg) {\n        if (!(key in fig.buttons)) {\n            continue;\n        }\n        fig.buttons[key].disabled = !msg[key];\n        fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n    }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n    if (msg['mode'] === 'PAN') {\n        fig.buttons['Pan'].classList.add('active');\n        fig.buttons['Zoom'].classList.remove('active');\n    } else if (msg['mode'] === 'ZOOM') {\n        fig.buttons['Pan'].classList.remove('active');\n        fig.buttons['Zoom'].classList.add('active');\n    } else {\n        fig.buttons['Pan'].classList.remove('active');\n        fig.buttons['Zoom'].classList.remove('active');\n    }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n    // Called whenever the canvas gets updated.\n    this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n    return function socket_on_message(evt) {\n        if (evt.data instanceof Blob) {\n            var img = evt.data;\n            if (img.type !== 'image/png') {\n                /* FIXME: We get \"Resource interpreted as Image but\n                 * transferred with MIME type text/plain:\" errors on\n                 * Chrome.  But how to set the MIME type?  It doesn't seem\n                 * to be part of the websocket stream */\n                img.type = 'image/png';\n            }\n\n            /* Free the memory for the previous frames */\n            if (fig.imageObj.src) {\n                (window.URL || window.webkitURL).revokeObjectURL(\n                    fig.imageObj.src\n                );\n            }\n\n            fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n                img\n            );\n            fig.updated_canvas_event();\n            fig.waiting = false;\n            return;\n        } else if (\n            typeof evt.data === 'string' &&\n            evt.data.slice(0, 21) === 'data:image/png;base64'\n        ) {\n            fig.imageObj.src = evt.data;\n            fig.updated_canvas_event();\n            fig.waiting = false;\n            return;\n        }\n\n        var msg = JSON.parse(evt.data);\n        var msg_type = msg['type'];\n\n        // Call the  \"handle_{type}\" callback, which takes\n        // the figure and JSON message as its only arguments.\n        try {\n            var callback = fig['handle_' + msg_type];\n        } catch (e) {\n            console.log(\n                \"No handler for the '\" + msg_type + \"' message type: \",\n                msg\n            );\n            return;\n        }\n\n        if (callback) {\n            try {\n                // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n                callback(fig, msg);\n            } catch (e) {\n                console.log(\n                    \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n                    e,\n                    e.stack,\n                    msg\n                );\n            }\n        }\n    };\n};\n\nfunction getModifiers(event) {\n    var mods = [];\n    if (event.ctrlKey) {\n        mods.push('ctrl');\n    }\n    if (event.altKey) {\n        mods.push('alt');\n    }\n    if (event.shiftKey) {\n        mods.push('shift');\n    }\n    if (event.metaKey) {\n        mods.push('meta');\n    }\n    return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n    return Object.keys(original).reduce(function (obj, key) {\n        if (typeof original[key] !== 'object') {\n            obj[key] = original[key];\n        }\n        return obj;\n    }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n    if (name === 'button_press') {\n        this.canvas.focus();\n        this.canvas_div.focus();\n    }\n\n    // from https://stackoverflow.com/q/1114465\n    var boundingRect = this.canvas.getBoundingClientRect();\n    var x = (event.clientX - boundingRect.left) * this.ratio;\n    var y = (event.clientY - boundingRect.top) * this.ratio;\n\n    this.send_message(name, {\n        x: x,\n        y: y,\n        button: event.button,\n        step: event.step,\n        modifiers: getModifiers(event),\n        guiEvent: simpleKeys(event),\n    });\n\n    return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n    // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n    // Prevent repeat events\n    if (name === 'key_press') {\n        if (event.key === this._key) {\n            return;\n        } else {\n            this._key = event.key;\n        }\n    }\n    if (name === 'key_release') {\n        this._key = null;\n    }\n\n    var value = '';\n    if (event.ctrlKey && event.key !== 'Control') {\n        value += 'ctrl+';\n    }\n    else if (event.altKey && event.key !== 'Alt') {\n        value += 'alt+';\n    }\n    else if (event.shiftKey && event.key !== 'Shift') {\n        value += 'shift+';\n    }\n\n    value += 'k' + event.key;\n\n    this._key_event_extra(event, name);\n\n    this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n    return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n    if (name === 'download') {\n        this.handle_save(this, null);\n    } else {\n        this.send_message('toolbar_button', { name: name });\n    }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n    this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n    // Create a \"websocket\"-like object which calls the given IPython comm\n    // object with the appropriate methods. Currently this is a non binary\n    // socket, so there is still some room for performance tuning.\n    var ws = {};\n\n    ws.binaryType = comm.kernel.ws.binaryType;\n    ws.readyState = comm.kernel.ws.readyState;\n    function updateReadyState(_event) {\n        if (comm.kernel.ws) {\n            ws.readyState = comm.kernel.ws.readyState;\n        } else {\n            ws.readyState = 3; // Closed state.\n        }\n    }\n    comm.kernel.ws.addEventListener('open', updateReadyState);\n    comm.kernel.ws.addEventListener('close', updateReadyState);\n    comm.kernel.ws.addEventListener('error', updateReadyState);\n\n    ws.close = function () {\n        comm.close();\n    };\n    ws.send = function (m) {\n        //console.log('sending', m);\n        comm.send(m);\n    };\n    // Register the callback with on_msg.\n    comm.on_msg(function (msg) {\n        //console.log('receiving', msg['content']['data'], msg);\n        var data = msg['content']['data'];\n        if (data['blob'] !== undefined) {\n            data = {\n                data: new Blob(msg['buffers'], { type: data['blob'] }),\n            };\n        }\n        // Pass the mpl event to the overridden (by mpl) onmessage function.\n        ws.onmessage(data);\n    });\n    return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n    // This is the function which gets called when the mpl process\n    // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n    var id = msg.content.data.id;\n    // Get hold of the div created by the display call when the Comm\n    // socket was opened in Python.\n    var element = document.getElementById(id);\n    var ws_proxy = comm_websocket_adapter(comm);\n\n    function ondownload(figure, _format) {\n        window.open(figure.canvas.toDataURL());\n    }\n\n    var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n    // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n    // web socket which is closed, not our websocket->open comm proxy.\n    ws_proxy.onopen();\n\n    fig.parent_element = element;\n    fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n    if (!fig.cell_info) {\n        console.error('Failed to find cell for figure', id, fig);\n        return;\n    }\n    fig.cell_info[0].output_area.element.on(\n        'cleared',\n        { fig: fig },\n        fig._remove_fig_handler\n    );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n    var width = fig.canvas.width / fig.ratio;\n    fig.cell_info[0].output_area.element.off(\n        'cleared',\n        fig._remove_fig_handler\n    );\n    fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n    // Update the output cell to use the data from the current canvas.\n    fig.push_to_output();\n    var dataURL = fig.canvas.toDataURL();\n    // Re-enable the keyboard manager in IPython - without this line, in FF,\n    // the notebook keyboard shortcuts fail.\n    IPython.keyboard_manager.enable();\n    fig.parent_element.innerHTML =\n        '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n    fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n    fig.send_message('closing', msg);\n    // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n    // Turn the data on the canvas into data in the output cell.\n    var width = this.canvas.width / this.ratio;\n    var dataURL = this.canvas.toDataURL();\n    this.cell_info[1]['text/html'] =\n        '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n    // Tell IPython that the notebook contents must change.\n    IPython.notebook.set_dirty(true);\n    this.send_message('ack', {});\n    var fig = this;\n    // Wait a second, then push the new image to the DOM so\n    // that it is saved nicely (might be nice to debounce this).\n    setTimeout(function () {\n        fig.push_to_output();\n    }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n    var fig = this;\n\n    var toolbar = document.createElement('div');\n    toolbar.classList = 'btn-toolbar';\n    this.root.appendChild(toolbar);\n\n    function on_click_closure(name) {\n        return function (_event) {\n            return fig.toolbar_button_onclick(name);\n        };\n    }\n\n    function on_mouseover_closure(tooltip) {\n        return function (event) {\n            if (!event.currentTarget.disabled) {\n                return fig.toolbar_button_onmouseover(tooltip);\n            }\n        };\n    }\n\n    fig.buttons = {};\n    var buttonGroup = document.createElement('div');\n    buttonGroup.classList = 'btn-group';\n    var button;\n    for (var toolbar_ind in mpl.toolbar_items) {\n        var name = mpl.toolbar_items[toolbar_ind][0];\n        var tooltip = mpl.toolbar_items[toolbar_ind][1];\n        var image = mpl.toolbar_items[toolbar_ind][2];\n        var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n        if (!name) {\n            /* Instead of a spacer, we start a new button group. */\n            if (buttonGroup.hasChildNodes()) {\n                toolbar.appendChild(buttonGroup);\n            }\n            buttonGroup = document.createElement('div');\n            buttonGroup.classList = 'btn-group';\n            continue;\n        }\n\n        button = fig.buttons[name] = document.createElement('button');\n        button.classList = 'btn btn-default';\n        button.href = '#';\n        button.title = name;\n        button.innerHTML = '<i class=\"fa ' + image + ' fa-lg\"></i>';\n        button.addEventListener('click', on_click_closure(method_name));\n        button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n        buttonGroup.appendChild(button);\n    }\n\n    if (buttonGroup.hasChildNodes()) {\n        toolbar.appendChild(buttonGroup);\n    }\n\n    // Add the status bar.\n    var status_bar = document.createElement('span');\n    status_bar.classList = 'mpl-message pull-right';\n    toolbar.appendChild(status_bar);\n    this.message = status_bar;\n\n    // Add the close button to the window.\n    var buttongrp = document.createElement('div');\n    buttongrp.classList = 'btn-group inline pull-right';\n    button = document.createElement('button');\n    button.classList = 'btn btn-mini btn-primary';\n    button.href = '#';\n    button.title = 'Stop Interaction';\n    button.innerHTML = '<i class=\"fa fa-power-off icon-remove icon-large\"></i>';\n    button.addEventListener('click', function (_evt) {\n        fig.handle_close(fig, {});\n    });\n    button.addEventListener(\n        'mouseover',\n        on_mouseover_closure('Stop Interaction')\n    );\n    buttongrp.appendChild(button);\n    var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n    titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n    var fig = event.data.fig;\n    if (event.target !== this) {\n        // Ignore bubbled events from children.\n        return;\n    }\n    fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n    el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n    // this is important to make the div 'focusable\n    el.setAttribute('tabindex', 0);\n    // reach out to IPython and tell the keyboard manager to turn it's self\n    // off when our div gets focus\n\n    // location in version 3\n    if (IPython.notebook.keyboard_manager) {\n        IPython.notebook.keyboard_manager.register_events(el);\n    } else {\n        // location in version 2\n        IPython.keyboard_manager.register_events(el);\n    }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n    // Check for shift+enter\n    if (event.shiftKey && event.which === 13) {\n        this.canvas_div.blur();\n        // select the cell after this one\n        var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n        IPython.notebook.select(index + 1);\n    }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n    fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n    // Return the cell and output element which can be found *uniquely* in the notebook.\n    // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n    // IPython event is triggered only after the cells have been serialised, which for\n    // our purposes (turning an active figure into a static one), is too late.\n    var cells = IPython.notebook.get_cells();\n    var ncells = cells.length;\n    for (var i = 0; i < ncells; i++) {\n        var cell = cells[i];\n        if (cell.cell_type === 'code') {\n            for (var j = 0; j < cell.output_area.outputs.length; j++) {\n                var data = cell.output_area.outputs[j];\n                if (data.data) {\n                    // IPython >= 3 moved mimebundle to data attribute of output\n                    data = data.data;\n                }\n                if (data['text/html'] === html_output) {\n                    return [cell, data, j];\n                }\n            }\n        }\n    }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n    IPython.notebook.kernel.comm_manager.register_target(\n        'matplotlib',\n        mpl.mpl_figure_comm\n    );\n}\n",
      "text/plain": [
       "<IPython.core.display.Javascript object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<div id='28f94559-54de-4301-bddd-8ae9acf2d499'></div>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "[[<Figure size 640x480 with 4 Axes>]]"
      ]
     },
     "execution_count": 56,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 输出结果  \n",
    "cerebro.plot(style='bar',volume=False)  # 可以选择生成图表"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "初始资金: 1000000\n",
      "回测期间：20000403:20240522\n",
      "总资金: 1004274.2\n",
      "净收益: 4274.2\n",
      "总交易次数: 240\n",
      "盈利次数: 117\n",
      "胜率%: 48.75\n",
      "年度汇报: OrderedDict([(1991, 0.00010904000000011571), (1992, 0.000631891098594517), (1993, 0.0006934961193754141), (1994, -0.0004153639485803273), (1995, 4.2216978787701365e-05), (1996, 0.00033042931206672677), (1997, -3.563039927756417e-05), (1998, 2.5944808202504888e-05), (1999, 0.0003142356038436933), (2000, 0.00026030824129574803), (2001, -3.007112644803911e-05), (2002, 0.00011055688171057731), (2003, -0.00015598203841393765), (2004, -7.906119497191888e-05), (2005, -5.748635555025139e-05), (2006, 0.0007106596501795792), (2007, 0.0010456306241490854), (2008, -0.00031004315920768466), (2009, 0.0012036352039950238), (2010, -0.0007145048923647979), (2011, 4.004247750644119e-05), (2012, 0.00022574923310103578), (2013, -0.00011078230686012436), (2014, 0.0007376277234538708), (2015, 0.00025477309590926644), (2016, -7.035973259339467e-05), (2017, 0.00015852456545029092), (2018, -0.00036122109807057434), (2019, 0.0007023660239322549), (2020, -8.11823912378884e-05), (2021, -0.00041750496537618087), (2022, -0.0002499786069587451), (2023, -0.0001566427875763221), (2024, -8.289877191736128e-05)])\n",
      "年化收益%: 0.013201596845630298\n",
      "最大回撤比例%: 0.16344186310379907\n",
      "夏普比率: -23.290527584255262\n",
      "累计收益： 0.00426509155217436\n",
      "最后投资金额：1004274.2\n"
     ]
    }
   ],
   "source": [
    "# 设置回测时间范围\n",
    "start_date = datetime(2000, 4, 3)\n",
    "end_date = datetime(2024, 5, 22)\n",
    "\n",
    "#获取回测结果并打印\n",
    "port_value = cerebro.broker.getvalue()\n",
    "pnl = port_value - start_cash\n",
    "print(f\"初始资金: {start_cash}\\n回测期间：{start_date.strftime('%Y%m%d')}:{end_date.strftime('%Y%m%d')}\")\n",
    "print(f\"总资金: {round(port_value, 2)}\")\n",
    "print(f\"净收益: {round(pnl, 2)}\")#计算胜率\n",
    "total_trades = results[0].analyzers.tradeanalyzer.get_analysis()['total']['total']\n",
    "won_trades = results[0].analyzers.tradeanalyzer.get_analysis()['won']['total']\n",
    "win_rate = (won_trades / total_trades) * 100 if total_trades > 0 else 0\n",
    "print('总交易次数:', total_trades)\n",
    "print('盈利次数:', won_trades)\n",
    "print('胜率%:', win_rate)\n",
    "# 打印分析器输出结果\n",
    "# print(f\"初始资金: {start_cash}\\n回测期间:{start_date.strftime('%Y-%m-%d')} : {end_date.strftime('%Y-%m-%d')}\")\n",
    "print('年度汇报:', results[0].analyzers.annualReturn.get_analysis())\n",
    "print('年化收益%:', results[0].analyzers.annualizedReturns.get_analysis()['rnorm100'])\n",
    "print('最大回撤比例%:', results[0].analyzers.drawdown.get_analysis().max.drawdown)\n",
    "print('夏普比率:', results[0].analyzers.sharpeRatio.get_analysis()['sharperatio'])\n",
    "print('累计收益：', results[0].analyzers.totalReturns.get_analysis()['rtot'])\n",
    "\n",
    "print(f'最后投资金额：{round(cerebro.broker.getvalue(), 2)}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 年度报告"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "年度汇报:\n",
      "1991, +0.00010904\n",
      "1992, +0.00063189\n",
      "1993, +0.00069350\n",
      "1994, -0.00041536\n",
      "1995, +0.00004222\n",
      "1996, +0.00033043\n",
      "1997, -0.00003563\n",
      "1998, +0.00002594\n",
      "1999, +0.00031424\n",
      "2000, +0.00026031\n",
      "2001, -0.00003007\n",
      "2002, +0.00011056\n",
      "2003, -0.00015598\n",
      "2004, -0.00007906\n",
      "2005, -0.00005749\n",
      "2006, +0.00071066\n",
      "2007, +0.00104563\n",
      "2008, -0.00031004\n",
      "2009, +0.00120364\n",
      "2010, -0.00071450\n",
      "2011, +0.00004004\n",
      "2012, +0.00022575\n",
      "2013, -0.00011078\n",
      "2014, +0.00073763\n",
      "2015, +0.00025477\n",
      "2016, -0.00007036\n",
      "2017, +0.00015852\n",
      "2018, -0.00036122\n",
      "2019, +0.00070237\n",
      "2020, -0.00008118\n",
      "2021, -0.00041750\n",
      "2022, -0.00024998\n",
      "2023, -0.00015664\n",
      "2024, -0.00008290\n"
     ]
    }
   ],
   "source": [
    "# 假设 annual_return_data 是您的 OrderedDict 数据  \n",
    "annual_return_data =  results[0].analyzers.annualReturn.get_analysis()\n",
    "  \n",
    "# 设定年份字段的最小宽度（不包括负号）和小数点后的位数  \n",
    "year_field_width = 4  # 假设年份最多4位  \n",
    "decimal_places = 8  # 小数点后的位数  \n",
    "  \n",
    "# 使用列表推导式来格式化字符串，确保小数点对齐  \n",
    "# 对于年份，我们不需要特殊处理，直接格式化即可  \n",
    "# 对于回报值，我们使用字符串格式化来确保它有一个固定的宽度，包括小数点和小数部分  \n",
    "lines = [f\"{year:>{year_field_width}d}, {return_value:+.{decimal_places}f}\" for year, return_value in annual_return_data.items()]  \n",
    "  \n",
    "# 使用 '\\n' 拼接这些行以换行  \n",
    "output = '\\n'.join(lines)  \n",
    "  \n",
    "# 打印结果  \n",
    "print('年度汇报:\\n' + output)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "base",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
